package txml;

import java.io.*;
import org.w3c.dom.*;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;

/**
 * Manages logging in xml for transactions.
 */
public class TransactionXmlLogger {
	/* Constants and enums */
	/**
	 * Transaction statuses.
	 */
	public enum TransactionStatus {
		TRANSACTION_ACTIVE {
			public String toString() {
				return "active";
			}
		},
		TRANSACTION_2PC {
			public String toString() {
				return "2pc";
			}
		},
		TRANSACTION_COMMITTED {
			public String toString() {
				return "committed";
			}
		},
		TRANSACTION_ABORTED {
			public String toString() {
				return "aborted";
			}
		}
	}
	/**
	 * Default name for the log file.
	 */
	public final static String DEFAULT_LOG_FILE_NAME = "transaction.log.xml";
	/**
	 * Default separator.
	 */
	public final static String DEFAULT_SEPARATOR = "_";
	/* Members */
	/**
	 * Chosen name of the log file, this is a prefix to which the transaction id will be added together with the extension. No file name direct access is provided.
	 */
	protected String logFileName;
	/**
	 * Id of the transaction, it will be appended to the filename.
	 */
	protected String transactionId;
	/* Constructors */
	/**
	 * Default constructor.
	 */
	public TransactionXmlLogger() {
		this.logFileName = DEFAULT_LOG_FILE_NAME;
		this.transactionId = "0";
	}
	/**
	 * Expliciting all.
	 */
	public TransactionXmlLogger(String fileName, String tid) {
		this.logFileName = fileName;
		this.transactionId = tid;
	}
	/* Accessors */
	/**
	 * Accessor for file name.
	 */
	public String getLogFileName() {
		return this.logFileName;
	}
	/**
	 * Accessor for file name.
	 */
	public void setLogFileName(String fileName) {
		this.logFileName = fileName;
	}
	/**status
	 * Accessor for transaction id.
	 */
	public String getTransactionId() {
		return this.transactionId;
	}
	/**
	 * Accessor for transaction id.
	 */
	public void setTransactionId(String tid) {
		this.transactionId = tid;
	}
	/* Methods */
	/**
	 * Getting complete file name: filename + separator + tid + .log.xml.
	 */
	public String completeFileName() {
            return this.logFileName + DEFAULT_SEPARATOR + transactionId.replace(":", "_") + ".log.xml";
	}
	/**
	 * Provides access to the transaction status. Read status.
	 */
	public TransactionStatus getTransactionStatus() throws TransactionXmlLoggerException {
		try {
			Element root = this.getRoot();
			String status = root.getAttribute("status");
			if (status.equals(TransactionStatus.TRANSACTION_2PC.toString())) return TransactionStatus.TRANSACTION_2PC;
			if (status.equals(TransactionStatus.TRANSACTION_ABORTED.toString())) return TransactionStatus.TRANSACTION_ABORTED;
			if (status.equals(TransactionStatus.TRANSACTION_ACTIVE.toString())) return TransactionStatus.TRANSACTION_ACTIVE;
			if (status.equals(TransactionStatus.TRANSACTION_COMMITTED.toString())) return TransactionStatus.TRANSACTION_COMMITTED;
			else throw new TransactionXmlLoggerException("Not recognized status: " + status);
		} catch(Exception e) {
			throw new TransactionXmlLoggerException("Error trying to set transaction status: " + e.getMessage());
		}
	}
	/**
	 * Provides access to the transaction status. Write status.
	 */
	public void setTransactionStatus(TransactionStatus transStatus) throws TransactionXmlLoggerException {
		try {
			Element root = this.getRoot();
			root.setAttribute("status", transStatus.toString());
			// Write edits
			this.writeDomToFile(root);
		} catch(Exception e) {
			throw new TransactionXmlLoggerException("Error trying to set transaction status: " + e.getMessage());
		}
	}
	/**
	 * Call for logging a value. Adds a value basing on its name and placing inside Before and After according to specified values.
	 */
	public void addValue(String name, String before, String after) throws TransactionXmlLoggerException {
		try {
			Element root = this.getRoot();
			Element values = (Element)(root.getElementsByTagName("Values").item(0));
			Element newval = root.getOwnerDocument().createElement("Value");
			newval.setAttribute("name", name);
			values.appendChild(newval);
			Element beforeel = root.getOwnerDocument().createElement("Before");
			beforeel.setTextContent(before);
			newval.appendChild(beforeel);
			Element afterel = root.getOwnerDocument().createElement("After");
			afterel.setTextContent(after);
			newval.appendChild(afterel);
			// Write
			this.writeDomToFile(root);
		} catch(Exception e) {
			throw new TransactionXmlLoggerException("Error trying to add value: " + e.getMessage());
		}
	}
	/**
	 * Call for getting the present value for a specified value name.
	 */
	public String getValueAfter(String name) throws TransactionXmlLoggerException {
		try {
			Element root = this.getRoot();
			Element value = TransactionXmlLogger.getValueByName(root, name);
			if (value != null) return ((Element)(value.getElementsByTagName("After").item(0))).getTextContent();
			else return null;
		} catch(Exception e) {
			throw new TransactionXmlLoggerException("Error trying to get value after: " + e.getMessage());
		}
	}
	/**
	 * Call for getting the old value for a specified value name.
	 */
	public String getValueBefore(String name) throws TransactionXmlLoggerException {
		try {
			Element root = this.getRoot();
			Element value = this.getValueByName(root, name);
			if (value != null) return ((Element)(value.getElementsByTagName("Before").item(0))).getTextContent();
			else return null;
		} catch(Exception e) {
			throw new TransactionXmlLoggerException("Error trying to get value after: " + e.getMessage());
		}
	}
	/**
	 * Verifies whether the file exists.
	 */
	protected boolean fileExists() {
		File file = new File(this.completeFileName());
		return file.exists();
	}
	/**
	 * Gets an element, a value, using name as index. If no element is found then returns null. The method is static because it is not bounded to the object status.
	 */
	protected static Element getValueByName(Element root, String name) throws TransactionXmlLoggerException {
		try {
			Element values = (Element)(root.getElementsByTagName("Values").item(0));
			for (int i = 0; i < values.getElementsByTagName("Value").getLength(); i++) {
				Element iel = (Element)(values.getElementsByTagName("Value").item(i));
				if (iel.getAttribute("name").equals(name)) {
					return iel;
				}
			}
			return null;
		} catch(Exception e) {
			throw new TransactionXmlLoggerException("Error trying to get value by tid: " + e.getMessage());
		}
	}
	/**
	 * Returns the root in the xml file (normalization applied). If the file exists the root is returned, otherwise an element (new) is returned.
	 * This procedure opens the file if already exists and gets the content parsing it. The obtained dom is returned (the root element).
	 * If the file does not exist, then a new dom is created and passed.
	 */
	protected Element getRoot() throws TransactionXmlLoggerException {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			if (this.fileExists()) {
				Document doc = builder.parse(this.completeFileName());
				Element root = doc.getDocumentElement();
				root.normalize(); /* Normalizing dom */
				return root;
			}
			// File does not exists... need to create, here, the dom (initial dom).
			Document doc = builder.newDocument();
			Element newroot = doc.createElement("Transaction");
			newroot.setAttribute("tid", this.transactionId);
			newroot.setAttribute("status", TransactionStatus.TRANSACTION_ACTIVE.toString());
			doc.appendChild(newroot);
			Element values = doc.createElement("Values");
			newroot.appendChild(values);
			return newroot;
		} catch (Exception e) {
			throw new TransactionXmlLoggerException("Error getting root: " + e.getMessage());
		}
	}
	/**
	 * Writes to file the dom. This procedure writed a dom in the file stored in the class variable.
	 * This call causes the file to be open or created and open. Then, the specified content is placed there (ovewritten).
	 */
	protected void writeDomToFile(Element el) throws TransactionXmlLoggerException {
		try {
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.METHOD, "xml");
			trans.setOutputProperty(OutputKeys.INDENT, "yes");
			trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(2));
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(el);
			trans.transform(source, result);
			String xmlString = sw.toString(); /* Getting dom here */
			// Now writing
			FileWriter fw = new FileWriter(this.completeFileName());
			fw.write(xmlString);
			fw.flush();
			fw.close(); /* Whether file already exists or not the file is however generated and overwritten with the specified content */
		} catch(Exception e) {
			throw new TransactionXmlLoggerException("Error trying to write xml in log: " + e.getMessage());
		}
	}
}
